package cn.spdb.harrier.server.worker.task;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;

import org.apache.commons.lang3.StringUtils;

import cn.spdb.harrier.common.Constants;
import cn.spdb.harrier.common.utils.OSUtils;
import cn.spdb.harrier.common.utils.ThreadUtils;
import cn.spdb.harrier.server.entity.JobStepBean;

public class CommandTask extends AbstractTask {

	private Process process;

	public CommandTask(JobStepBean jobStepBean) {
		super(jobStepBean);
	}

	@Override
	public void handle() {
		try {
			buildProcessStart();
			parseProcessOutput();
			Integer processId = getProcessId(process);
			jobStepBean.setProcessId(processId);
			process.waitFor();
		} catch (IOException | InterruptedException e) {
			e.printStackTrace();
		} finally {
			setExitStatusCode(process.exitValue());
			process.destroyForcibly();
		}
	}

/**
 * 构建并启动一个外部进程来执行任务命令。
 *
 * 该方法根据当前操作系统类型（Windows/Linux/其他）构造适当的命令行指令，
 * 设置工作目录和环境变量，最终启动子进程。
 *
 * @throws IOException 如果创建进程失败或工作目录无效
 */
private void buildProcessStart() throws IOException {
    ProcessBuilder processBuilder = new ProcessBuilder();

    // 设置工作目录（如果配置了且存在）
    if (StringUtils.isNotBlank(jobStepBean.getWorkdir())) {
        File workDirFile = new File(jobStepBean.getWorkdir());
        if (workDirFile.exists()) {
            processBuilder.directory(workDirFile);
        }
    }

    // 合并标准输出和错误输出
    processBuilder.redirectErrorStream(true);

    List<String> command = new ArrayList<>();

    // 根据操作系统构建命令
    if (OSUtils.isWindows()) {
        // Windows 下使用 cmd /c 执行命令
        command.add("cmd");
        command.add("/c");
        command.add(jobStepBean.getRunCmd());
    } else if (OSUtils.isLinux()) {
        // Linux 下使用 bash -c 执行命令（原可加 sudo，现被注释）
        command.add("bash");
        command.add("-c");
        command.add(jobStepBean.getRunCmd());
    } else {
        // 其他情况直接使用命令列表
        command.addAll(jobStepBean.getRunCmdList());
    }

    // 设置命令行参数
    processBuilder.command(command);

    // 设置环境变量
    Map<String, String> env = jobStepBean.getEnvMap();
    processBuilder.environment().putAll(env);

    // 启动进程
    process = processBuilder.start();
}


/**
 * 解析并处理子进程的标准输出和错误输出流。
 *
 * 此方法创建了一个固定大小的守护线程池，用于并发读取进程的输入流和错误流，
 * 避免因为缓冲区满而导致进程阻塞。每个流的读取操作都在单独的线程中执行，
 * 通过日志记录器将输出内容按行打印出来。
 */
private void parseProcessOutput() {
    // 创建一个包含两个线程的守护线程池，用于处理进程的输出流和错误流
    ExecutorService getOutputLogService = ThreadUtils.newDaemonFixedThreadPool(2,
            CommandTask.class.getSimpleName() + "-" + "getOutputLogService");

    // 提交任务：读取进程的标准输出流
    getOutputLogService.submit(() -> {
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
            // 按行读取并记录日志
            for (String line = reader.readLine(); line != null; logger.info(line), line = reader.readLine())
                ;
        } catch (Exception e) {
            e.printStackTrace();
        }
    });

    // 提交任务：读取进程的错误输出流
    getOutputLogService.submit(() -> {
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(process.getErrorStream(), StandardCharsets.UTF_8))) {
            // 按行读取并记录日志（使用 error 级别）
            for (String line = reader.readLine(); line != null; logger.error(line), line = reader.readLine())
                ;
        } catch (Exception e) {
            e.printStackTrace();
        }
    });
}


	private int getProcessId(Process process) {
		Long processId = 0L;
		try {
			Field f = process.getClass().getDeclaredField(Constants.PID);
			f.setAccessible(true);
			processId = f.getLong(process);
		} catch (Throwable e) {
			logger.error(e.getMessage(), e);
		}
		return processId.intValue();
	}

	@Override
	protected void subkill() {
		if (process == null) {
			return;
		}
		process.destroyForcibly();
	}
}
